package cmich.cps.svn.utility;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

import cmich.cps.svn.utility.gui.Prompts;

/**
 * The Class Settings.
 *
 * @author Jonathan Kissinger <kissi1j@cmich.edu>
 */
public class Settings {
	
	/** The Constant instance. */
	private static final Settings instance = new Settings();
	
	/** The date format. */
	public final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 'at' hh:mm:ss aaaa");
	
	/** The Constant LOGGER. */
	private final static Logger LOGGER = Logger.getLogger(Settings.class.getName());
	
	/** The subversion location. */
	private String subversionLocation;
	
	/** The my directory. */
	private File myDirectory;
	
	/** The prompts. */
	private Prompts prompts;
	
	/** The system. */
	private UtilitySystem system;
	
	/** The properties file. */
	private File propertiesFile;
	
	/** The local repo directory. */
	private File localRepoDirectory;
	
	/** The multi line comment. */
	private boolean multiLineComment;
	
	/** The username. */
	private String username = "";
	
	/** The password. */
	private String password = "";
	
	/** The authcache. */
	private Boolean authcache;

	/** Whether the computer is running cygwin */
	private boolean cygwin;

	/**
	 * Instantiates a new settings.
	 */
	private Settings() {}

	/**
	 * Gets the single instance of Settings.
	 *
	 * @return single instance of Settings
	 */
	public static Settings getInstance() {
		return instance;
	}

	/**
	 * Initializes variables.
	 */
	public void init(File directory) {
		LOGGER.finest("Initializing Settings.");
		this.system = UtilitySystem.getInstance();
		this.prompts = Prompts.getInstance();
		this.myDirectory = directory;
	}
	
	/**
	 * Startup.
	 */
	public void startup() {
		loadFileSettings();
		setSubversion();
		saveFileSettings();
		loadRepositories();
	}

	/**
	 * Loads the settings stored in the settings.prop file
	 */
	public void loadFileSettings() {
		this.propertiesFile = new File(this.myDirectory + "/settings.prop");
		if(this.propertiesFile.exists()) {
			Properties prop = new Properties();
			try {
				prop.load(new FileInputStream(this.propertiesFile));
				this.subversionLocation = prop.getProperty("subversionLocation");
				this.localRepoDirectory = new File(prop.getProperty("localRepoDirectory"));
				this.authcache = Boolean.parseBoolean(prop.getProperty("authCache"));
			} catch (IOException e) {
				LOGGER.severe(e.toString());
				prompts.fatalError("Error getting properties file settings.prop: " + e.toString());
			}
			if(!this.localRepoDirectory.exists()) {
				if(!this.localRepoDirectory.mkdir()) {
					String msg = "Failed to create local repository directory: " + this.localRepoDirectory;
					LOGGER.warning(msg);
					prompts.nonFatalError(msg);
					this.localRepoDirectory = new File(prompts.getLocalRepoDirectory());
				}
			}
		}
		else {
			initializeProperties();
		}
	}	

	/**
	 * Initializes the properties file if it does not yet exist.
	 */
	private void initializeProperties() {
		this.subversionLocation = "";
		this.localRepoDirectory = new File(prompts.getLocalRepoDirectory());
		this.authcache = false;
	}

	/**
	 * Ensures that subversion can be accessed by this utility.
	 * If it cannot it prompts for the location and saves it in the settings file.
	 */
	public void setSubversion() {
		if(!system.execCommand(svn()).contains(" help' for usage.")) {
			LOGGER.fine("Unable to execute svn, path not set or subversion location not set.");
			this.subversionLocation = prompts.setSubversion();
		}
		else {
			return;
		}
		setSubversion();
	}

	/**
	 * Stores the changed settings in the settings.prop file
	 */
	private void saveFileSettings() {
		Properties prop = new Properties();
		prop.setProperty("subversionLocation", this.subversionLocation);
		prop.setProperty("localRepoDirectory", this.localRepoDirectory.getAbsolutePath());
		prop.setProperty("authCache", this.authcache.toString());
		try {
			prop.store(new FileOutputStream(this.propertiesFile), "Cmich SVN utility properties file");
		} catch (IOException e) {
			LOGGER.warning(e.toString());
		}
	}

	/**
	 * Load repositories.
	 */
	private void loadRepositories() {
		LOGGER.fine("Loading repositories.");
		File[] dirs = this.localRepoDirectory.listFiles();
		for(File dir : dirs) {
			LOGGER.fine(dir.getPath());
			if(dir.isDirectory()) {
				Repository repo = new Repository(dir);
				if(repo.status()) {
					system.addRepo(repo);
					repo.update();
				}
			}
		}
	}
	
	/**
	 * Gets the java source.
	 *
	 * @param srcFile the src file
	 * @return the java source
	 */
	public String getJavaSource(File srcFile) {
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(srcFile));
		} catch (FileNotFoundException e) {
			LOGGER.info(e.toString());
			prompts.nonFatalError(e.toString());
			return null;
		}
		String line;
		String source = "";
		multiLineComment = false;
		try {
			line = reader.readLine();
			while(line!=null) {
				line = line.trim();
				source = source + checkStartComment(line);
				source = source + removeComments(line);
				source = source + checkEndComment(line);
				line = reader.readLine();
			}
			reader.close();
		} catch (IOException e) {
			String msg = "IOerror attempting to read file " + srcFile.getName() + ": " + e.toString();
			LOGGER.info(msg);
			prompts.nonFatalError(msg);
			return null;
		}		
		LOGGER.finest("Parsed source from " + srcFile + ":\n\n" + source);
		return source;
	}

	/**
	 * Removes the comments.
	 *
	 * @param line the line
	 * @return the string
	 */
	private String removeComments(String line) {
		String result = "";
		if(!line.startsWith("//") && !multiLineComment) {
			result = line;
			if(line.contains("//")) {
				result = line.split("//")[0];
			}
		}
		return result;
	}

	/**
	 * Check end comment.
	 *
	 * @param line the line
	 * @return the string
	 */
	private String checkEndComment(String line) {
		String result = "";
		if(multiLineComment && line.contains("*/")) {
			if(!line.startsWith("*/")  && !line.endsWith("*/")) {
				result = line.split("*/")[1];
			}
			multiLineComment = false;
		}
		return result;
	}

	/**
	 * Check start comment.
	 *
	 * @param line the line
	 * @return the string
	 */
	private String checkStartComment(String line) {
		String result = "";
		if(line.contains("/*")) {
			if(!line.startsWith("/*")) {
				 result = line.split("/*")[0];
			}
			multiLineComment = true;
		}
		return result;
	}
	
	/**
	 * Gets the package.
	 *
	 * @param source the source
	 * @return the package
	 */
	public String getPackage(String source) {
		String[] packages = source.split("package ");
		if(packages.length==2) {
			String myPackage = packages[1].split(";")[0];
			LOGGER.fine("Found package: " + myPackage);
			return myPackage;
		}
		return null;
	}
	
	/**
	 * Gets the package plus the file name.
	 *
	 * @param file the file
	 * @return the package plus the file name
	 */
	public String getPackagePlusFileName(File file) {
		String source = getJavaSource(file);
		String myPackage = getPackage(source);
		return myPackage.replace(".", File.separator) + File.separator + file.getName();
	}
	
	/**
	 * Gets the test names.
	 *
	 * @param source the source
	 * @param myPackage the my package
	 * @return the test names
	 */
	public List<String> getTestNames(String source, String myPackage) {
		LOGGER.fine("Source: " + source);
		String[] tests = source.split("@Test\\s*public void |@Test\\(timeout=\\d*\\)\\s*public void ");
		List<String> testNames = new ArrayList<String>();
		for(String test : tests) {
			String testName = test.split("\\(\\)")[0];
			testNames.add(testName + "(" + myPackage + ")");
		}
		testNames.remove(0);
		LOGGER.fine("Found " + testNames.size() + " tests in " + myPackage);
		return testNames;
	}
	
	/**
	 * The subversion executable.
	 *
	 * @return the string representing the path to subversion the subversion executable
	 */
	public String svn() {
		if(this.subversionLocation.isEmpty()) {
			return "svn";
		}
		return this.subversionLocation + File.separator + "svn";
	}

	/**
	 * Gets the local repo directory.
	 *
	 * @return the local repo directory
	 */
	public File getLocalRepoDirectory() {
		return localRepoDirectory;
	}

	/**
	 * Sets the local repo directory.
	 *
	 * @param localRepoDirectory the new local repo directory
	 */
	public void setLocalRepoDirectory(File localRepoDirectory) {
		this.localRepoDirectory = localRepoDirectory;
	}

	/**
	 * Gets the subversion location.
	 *
	 * @return the subversion location
	 */
	public String getSubversionLocation() {
		return subversionLocation;
	}

	/**
	 * Sets the user pass.
	 */
	public void setUserPass() {
		prompts.login();
	}
	
	/**
	 * Gets the username.
	 *
	 * @return the username
	 */
	public String getUsername() {
		if(!username.isEmpty()) {
			return " --username " + username;
		}
		return username;
	}

	/**
	 * Gets the password.
	 *
	 * @return the password
	 */
	public String getPassword() {
		if(!password.isEmpty()) {
			return " --password " + password;
		}
		return password;
	}

	/**
	 * Sets the username.
	 *
	 * @param username the new username
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * Sets the password.
	 *
	 * @param password the new password
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Gets the auth cache.
	 *
	 * @return the auth cache
	 */
	public boolean getAuthCache() {
		return this.authcache;
	}
	
	/**
	 * Sets the auth cache.
	 *
	 * @param authcache the new auth cache
	 */
	public void setAuthCache(boolean authcache) {
		this.authcache = authcache;
	}

	public boolean isCygwin() {
		return this.cygwin;
	}

	public void setCygwin() {
		this.cygwin = true;
	}

	public String enclose(String string) {
		if(isWindows()) {
			return "\"" + string + "\"";
		}
		return string;
	}

	public boolean isWindows() {
		if(System.getProperty("os.name").toLowerCase().contains("win")) {
			return true;
		}
		return false;
	}
}
